#define CODE_ANALYSIS

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Diagnostics;
using System.Globalization;
using System.Diagnostics.CodeAnalysis;
using System.Xml;
using System.Xml.XPath;
using System.Collections.ObjectModel;
using System.IO;

[assembly: CLSCompliant(true)]
namespace DBSetupHelper
{
    static class Program
    {
        /// <summary>
        /// Main entry point of the program.
        /// </summary>
        /// <param name="args">Arguments supplied by the user.</param>
        /// <remarks>
        /// Run without parameters to get help.
        /// </remarks>
        [SuppressMessage("Microsoft.Globalization", "CA1303")] //get around not using resource files.
        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                PrintHelp();
            }
            else
            {
                InstallParms installParms = ParseCommandLineArgs(args);

                if (installParms.ShowHelp)
                {
                    PrintHelp();
                }
                else
                {
                    if (installParms.InteractiveInstall)
                        InteractiveRun(installParms);
                    else
                        AutomatedRun(installParms);

                    if (installParms.DebugMode)
                    {
                        Console.Out.WriteLine("Please press enter to exit.");
                        Console.In.ReadLine();
                    }
                }
            }
        }

        private static InstallParms ParseCommandLineArgs(string[] args)
        {
            InstallParms installParms = new InstallParms();

            for (int i = 0; i < args.Length; i = i + 2)
            {
                switch (args[i])
                {
                    case "-d":
                        if (args.Length > i + 1
                                && !args[i + 1].StartsWith("-"))
                        {
                            installParms.TargetDir = args[i + 1];
                        }
                        else
                        {
                            throw new MisusedArgumentException();
                        }
                        break;
                    case "-e":
                        {
                            if (args.Length > i + 1
                                && !args[i + 1].StartsWith("-"))
                            {
                                installParms.ExtractOnly = bool.Parse(args[i + 1]);
                            }
                            else
                            {
                                installParms.ExtractOnly = true;
                                i--;
                            }
                        }
                        break;
                    case "-i":
                        {
                            if (args.Length > i + 1
                                && !args[i + 1].StartsWith("-"))
                            {
                                installParms.InteractiveInstall = bool.Parse(args[i + 1]);
                            }
                        }
                        break;
                    case "-p":
                        if (args.Length > i + 1
                                && !args[i + 1].StartsWith("-"))
                        {
                            installParms.Password = args[i + 1];
                        }
                        else
                        {
                            throw new MisusedArgumentException();
                        }
                        break;
                    case "-s":
                        if (args.Length > i + 1
                                && !args[i + 1].StartsWith("-"))
                        {
                            installParms.Server = args[i + 1];
                        }
                        else
                        {
                            throw new MisusedArgumentException();
                        }
                        break;
                    case "-t":
                        {
                            if (args.Length > i + 1
                                && !args[i + 1].StartsWith("-"))
                            {
                                installParms.TestInstall = bool.Parse(args[i + 1]);
                            }
                            else
                            {
                                installParms.TestInstall = false;
                                i--;
                            }
                        }
                        break;
                    case "-u":
                        if (args.Length > i + 1
                                && !args[i + 1].StartsWith("-"))
                        {
                            installParms.User = args[i + 1];
                        }
                        else
                        {
                            throw new MisusedArgumentException();
                        }
                        break;
                    case "-U":
                        {
                            if (args.Length > i + 1
                                    && !args[i + 1].StartsWith("-"))
                            {
                                installParms.InteractiveInstall = !bool.Parse(args[i + 1]);
                            }
                            else
                            {
                                installParms.InteractiveInstall = true;
                                i--;
                            }
                        }
                        break;
                    case "-DEBUG":
                        installParms.DebugMode = true;
                        i--;
                        break;
                    case "/?":
                    default:
                        installParms.ShowHelp = true;
                        break;
                }
            }
            return installParms;
        }

        private static void PrintHelp()
        {
            Console.Out.WriteLine("Help:");
            Console.Out.WriteLine();
            Console.Out.WriteLine(" /?  -- Prints this help message.");
            Console.Out.WriteLine(" -d  -- Target directory. Directory where scripts reside.");
            Console.Out.WriteLine(" -e  -- Extract Only. Exits application after extract from MSI.");
            Console.Out.WriteLine(" -i  -- Interactive install. Collects info. from the user.");
            Console.Out.WriteLine(" -p  -- Password. Password for specified user.");
            Console.Out.WriteLine(" -s  -- Server. Server to connect to for scripts to run against.");
            Console.Out.WriteLine(" -t  -- Test Install. Runs optional test scripts as well.");
            Console.Out.WriteLine(" -u  -- User name. User name to connect to SQL database with.");
            Console.Out.WriteLine(" -U  -- Unattended. Runs the system without prompting for user input.");
            Console.Out.WriteLine(" -DEBUG  -- Pipes all output to the command window and halts before exit.");
            Console.Out.WriteLine();
            Console.Out.WriteLine(" e.g.");
            Console.Out.WriteLine("  DBSetupHelper -d \"C:\\My Dir\\Blah\" -u user -p password -s CompName\\SQLEXPRESS");
            Console.Out.WriteLine("  DBSetupHelper -i true");
            Console.Out.WriteLine("  DBSetupHelper -d \"C:\\My Dir\\Blah\" -i true");
            Console.Out.WriteLine("  DBSetupHelper -d \"C:\\My Dir\\Blah\" -i true -t true");
            Console.Out.WriteLine();
        }

        private static void InteractiveRun(InstallParms installParms)
        {
            SettingsDialog settingsDialog = new SettingsDialog();
            settingsDialog.Server = installParms.Server;
            settingsDialog.UserName = installParms.User;
            settingsDialog.Password = installParms.Password;

            settingsDialog.ShowDialog();

            installParms.Server = settingsDialog.Server;
            installParms.User = settingsDialog.UserName;
            installParms.Password = settingsDialog.Password;
            installParms.ExtractOnly = settingsDialog.ExtractOnly;
            installParms.CancelInstall = settingsDialog.CancelInstall;

            AutomatedRun(installParms);
        }

        private static void AutomatedRun(InstallParms installParms)
        {
            //load xml file
            XPathDocument xmlControlDoc;
            if (string.IsNullOrEmpty(installParms.TargetDir))
                xmlControlDoc = new XPathDocument(Environment.CurrentDirectory + "\\Control.xml");
            else
                xmlControlDoc = new XPathDocument(installParms.TargetDir + "\\Control.xml");
            XPathNavigator xmlControlNav = xmlControlDoc.CreateNavigator();
            RunParms runParms = GenerateRunParms(xmlControlNav);
            Queue<DBScript> scriptPattern;

            //overload defaults with command line specified values.
            if (!string.IsNullOrEmpty(installParms.User))
                runParms.UserName = installParms.User;
            if (!string.IsNullOrEmpty(installParms.Password))
                runParms.Password = installParms.Password;
            if (!string.IsNullOrEmpty(installParms.Server))
                runParms.Server = installParms.Server;


            //TODO: verify command type to run and fail gracefully if it is not.
            if (!ExecCheck(runParms.CommandLineExe))
            { return; }

            //generate list of sql files to run. 
            scriptPattern = GenerateScriptPattern(xmlControlNav, installParms);

            if (installParms.CancelInstall)
            {
                foreach (DBScript script in scriptPattern)
                {
                    System.IO.File.Delete(script.ScriptPath);
                }
            }
            else if (!installParms.ExtractOnly)
            {
                //off off and away.
                RunScriptPattern(installParms, runParms, scriptPattern);
            }
        }

        private static void RunScriptPattern(InstallParms installParms, RunParms runParms, Queue<DBScript> scriptPattern)
        {
            //Setup
            bool error = false;
            Process process = new Process();
            process.StartInfo.FileName = runParms.CommandLineExe;
            process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
            process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.UseShellExecute = false;
            StreamReader processOutput = null;
            StringBuilder errorLogBuffer = new StringBuilder();

            string staticArg = "-b -U " + runParms.UserName + " -P " + runParms.Password +
                " -S " + runParms.Server + " -i \"";
            staticArg = staticArg.Replace("%computername%", Environment.MachineName);

            int index = 0;

            foreach (DBScript script in scriptPattern)
                if (!script.Disabled)
                {
                    index++;
                    if (!installParms.TestInstall)
                    {
                        if (!script.TestInstall)
                        {
                            Console.Out.WriteLine("Running script " + index + " of " + scriptPattern.Count);
                            process.StartInfo.Arguments = staticArg + script.ScriptPath + "\"";
                            process.Start();
                            process.WaitForExit();
                            processOutput = process.StandardOutput;
                        }
                        else
                        {
                            Console.Out.WriteLine("Skipping script " + index + " of " + scriptPattern.Count);
                            //remove script.
                            System.IO.File.Delete(script.ScriptPath);
                        }
                    }
                    else
                    {
                        Console.Out.WriteLine("Running script " + index + " of " + scriptPattern.Count);
                        process.StartInfo.Arguments = staticArg + script.ScriptPath + "\"";
                        process.Start();
                        process.WaitForExit();
                        processOutput = process.StandardOutput;
                    }
                    
                    if (installParms.DebugMode && processOutput != null)
                    {
                        Console.Out.WriteLine();
                        Console.Out.WriteLine(processOutput.ReadToEnd());
                    }

                    if (process.ExitCode != 0)
                    {
                        errorLogBuffer.Append(" -- ERROR -- ");
                        errorLogBuffer.Append(script.ScriptPath);
                        errorLogBuffer.Append(". ");
                        error = true;
                        break;
                    }
                    else
                    {
                        Console.Out.WriteLine();
                    }

                    //clean up processOutput for next run
                    if (processOutput != null)
                    {
                        processOutput.Dispose();
                        processOutput = null;
                    }
                }
                else
                {
                    Console.Out.WriteLine("Skipping script " + ++index + " of " + scriptPattern.Count);

                    //remove script.
                    System.IO.File.Delete(script.ScriptPath);
                }

            if (error)
            {
                errorLogBuffer.Append("Your installiation has errored. Please review the \"Control.xml\" before running");
                errorLogBuffer.Append("the setup helper again. This can be found at " + Environment.CurrentDirectory);
                errorLogBuffer.Append(Environment.NewLine);
                errorLogBuffer.Append(Environment.NewLine);
                errorLogBuffer.Append("Please press enter to continue.");
                Console.Out.WriteLine(errorLogBuffer.ToString());
                Console.In.ReadLine();
            }
        }

        private static RunParms GenerateRunParms(XPathNavigator xmlNavigator)
        {
            bool throwError = true;
            RunParms runParms = new RunParms();
            xmlNavigator.MoveToRoot();

            while (xmlNavigator.MoveToFirstChild() == true && xmlNavigator.Name != "Defaults")
            { ;}

            if (xmlNavigator.Name == "Defaults")
            {
                throwError = false;
                xmlNavigator.MoveToFirstChild();
                do
                {
                    switch (xmlNavigator.Name)
                    {
                        case "CommandLineExe":
                            runParms.CommandLineExe = xmlNavigator.Value;
                            break;
                        case "UserName":
                            runParms.UserName = xmlNavigator.Value;
                            break;
                        case "Password":
                            runParms.Password = xmlNavigator.Value;
                            break;
                        case "Server":
                            runParms.Server = xmlNavigator.Value;
                            break;
                    }
                }
                while (xmlNavigator.MoveToNext());
            }

            if(throwError)
                throw new MalformedControlXmlException();

            return runParms;
        }

        private static Queue<DBScript> GenerateScriptPattern(XPathNavigator xmlNavigator, InstallParms installParms)
        {
            bool throwError = true; 
            Queue<DBScript> runParms = new Queue<DBScript>();
            xmlNavigator.MoveToRoot();

            while (xmlNavigator.MoveToFirstChild() == true && xmlNavigator.Name != "Defaults")
            { ;}

            if (xmlNavigator.MoveToNext() && xmlNavigator.Name == "Scripts")
            {
                throwError = false;

                xmlNavigator.MoveToFirstChild(); //on script
                do
                {
                    string filePath;
                    bool disabled;
                    bool testInstall;

                    xmlNavigator.MoveToFirstChild(); //on FileName
                    if (!string.IsNullOrEmpty(installParms.TargetDir))
                        filePath = installParms.TargetDir + "\\" + xmlNavigator.Value;
                    else
                        filePath = xmlNavigator.Value;
                    xmlNavigator.MoveToNext(); //on Disabled
                    disabled = bool.Parse(xmlNavigator.Value);
                    xmlNavigator.MoveToNext(); //on TestInstallOnly
                    testInstall = bool.Parse(xmlNavigator.Value);
                    xmlNavigator.MoveToParent();

                    runParms.Enqueue(new DBScript(filePath, testInstall, disabled));
                }
                while (xmlNavigator.MoveToNext());
            }

            if (throwError)
                throw new MalformedControlXmlException();

            return runParms;
        }

        private static bool ExecCheck(string executableName)
        {
            try
            {
                Process process = new Process();
                process.StartInfo.FileName = executableName;
                process.StartInfo.Arguments = "/?";
                process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.UseShellExecute = false;
                process.Start();
                process.WaitForExit();
                
                //exec is installed on the machine.
                return true;
            }
            catch (Win32Exception ex)
            {
                if (ex.Message == "The system cannot find the file specified")
                { return false;}
                else
                { throw; }
            }
        }
    }
}
